#pragma once

#include <optional>
#include <functional>

#include "helper/Result.h"
#include "helper/ControlFlow.h"

namespace helper {
namespace iter {

template<typename T> class Skip;
template<typename T> class Take;

template<typename T>
class Iterator {
public:
  typedef T Item;

  virtual ~Iterator(){}
  virtual std::optional<Item> next() = 0;


  //* ***********  basement start *******************
  virtual Result<size_t> advanceBy(size_t n) {
    for(size_t i = 0; i < n; ++i){
      if(! next() ){
        return Result<size_t>::Error(i); 
      }
    }
    return Result<size_t>::OK();
  }

  template<typename B>
  B fold(const B & init,  const std::function<B(const B &, const T &)> & f){
    B accum = init;
    for(auto && o = next(); o; o = next()){
      accum = f(accum, o.value());
    }
    return accum;
  }

  //R = ControlFlow<B, B>();
  template<typename R, typename B> 
  R tryFold(const B & init, const std::function<R(const B &, const T &)> & f) {
    B accum = init;
    for(auto && o = next(); o; o = next()){
      R && r = f(accum, o.value());
      if(r.isBreak()){
        return r;
      }
      else{
        accum = r.continueValue().value();
        continue;
      }
    }
    return R::Continue(accum);
  }

  //* ***********  basement end  *******************


  //based-0
  virtual std::optional<Item> nth(size_t n){
    if(!advanceBy(n))  {
      return std::nullopt;
    }
    return next();
  }

  // call fold
  virtual void foreach(const std::function<void(const T &)> & f){
    auto call = [](const std::function<void(const T&)> & f) -> std::function<int(const int &, const Item & )>{
        return [&f](const int & , const Item &v) -> int{
          f(v);
          return 0;
        };
    };
    fold<int>(0, call(f));
  }
  //call fold
  virtual size_t count() {
    return fold<size_t>(0
      , [](const auto & accum, const auto & ){
        return accum + 1;
      });
  }

  //call tryFold
  virtual bool all(const std::function<bool(const T&)> & pred) {
    return tryFold<ControlFlowFlag, ControlFlowOpCode>(
      ControlFlowOpCode::BREAK,
      [&pred](const auto & , const auto & v) -> ControlFlowFlag {
        return pred(v) ? ControlFlowFlag::Continue(): ControlFlowFlag::Break();
      })
      .isContinue();
  }
  //call tryFold
  virtual bool any(const std::function<bool(const T&)> & pred) {
    return tryFold<ControlFlowFlag, ControlFlowOpCode>(
      ControlFlowOpCode::CONTINUE, 
      [&pred](const auto & , const auto & v) -> ControlFlowFlag {
        return pred(v) ? ControlFlowFlag::Break() : ControlFlowFlag::Continue();
      })
    .isBreak();
  }

  //call tryFold
  virtual std::optional<Item> find(const std::function<bool(const T&)> & pred) {
    return tryFold<ControlFlow<T, T>, T>(T(),
      [&pred](const auto &, const auto & v) -> ControlFlow<T, T> {
        return pred(v) ? ControlFlow<T, T>::Break(v) : ControlFlow<T, T>::Continue();
      }).breakValue();
  }

  //call tryFold
  template<typename B>
  std::optional<B> findMap(const std::function<std::optional<B>(const T&)> & f) {
    return tryFold<ControlFlow<B, B>, B>(
      B(),
      [&f](const auto &, const auto & v) -> ControlFlow<B, B>{
        std::optional<B> && b = f(v);
        return b ?  ControlFlow<B, B>::Break(b.value()) : ControlFlow<B, B>::Continue();
      })
    .breakValue();
  }


  //Skip<Item> skip(size_t n) ;
  Skip<Item> skip(size_t n)  {
    return Skip<Item>(*this, n);
  }
  Take<Item> take(size_t n) {
    return Take<Item>(*this, n);
  }
  



  //inspect
  //max/min, max_by/min, max_by_key/min_by_key
  //collect
  //take , take_while -> new Iterator
  //advance_by
  //enumerate
  //filter,filter_map
  //flat, flat_map
  //position, rposition
  //scan 
  //skip, skip_while
  //map, map_while, reduce, 
  //zip, unzip
};

}//namespace iter
}//namespace helper

//#include "helper/iter/Skip.h"
//#include "helper/iter/Take.h"
